use std::collections::btree_map::Keys;
use std::collections::BTreeMap;
use std::fmt::{Debug, Display, Formatter};
use std::time::SystemTime;
use bytestring::ByteString;

use symbol::Symbol;

use crate::entry_parser::parse_entry;
use crate::error::Error;
use crate::LResult;
use crate::value::FieldValue;

pub type TagKey = Symbol;
pub type TagValue = Symbol;

pub type FieldKey = Symbol;

pub struct Entry {
    pub measurement: String,
    pub ts: SystemTime,
    pub tags: BTreeMap<TagKey, TagValue>,
    pub fields: BTreeMap<FieldKey, FieldValue>,
}

#[derive(Debug)]
pub struct EntryBuilder {
    measurement: Option<String>,
    ts: Option<SystemTime>,
    tags: BTreeMap<TagKey, TagValue>,
    fields: BTreeMap<FieldKey, FieldValue>,
}

pub enum LabelType {
    Tag,
    Field,
    Other,
}

impl Entry {

    #[inline]
    pub fn parse(line: ByteString) -> LResult<Self> {
        parse_entry(&*line)
    }

    #[inline]
    pub fn new_builder<S: Into<String>>(measurement: S) -> EntryBuilder {
        EntryBuilder::create(measurement)
    }

    #[inline]
    pub fn create(s: &str) -> LResult<Self> {
        parse_entry(s)
    }

    #[inline]
    pub fn ts_nano(&self) -> u128 {
        self.ts.duration_since(SystemTime::UNIX_EPOCH).unwrap().as_nanos()
    }

    #[inline]
    pub fn ts(&self) -> SystemTime {
        self.ts
    }

    #[inline]
    pub fn measurement(&self) -> &String {
        &self.measurement
    }

    #[inline]
    pub fn get_tag(&self, key: &str) -> LResult<Option<&TagValue>> {
        let tag_key = key.try_into()?;
        Ok(self.tags.get(&tag_key))
    }

    #[inline]
    pub fn tag_names(&self) -> Keys<'_, TagKey, TagValue> {
        self.tags.keys()
    }

    #[inline]
    pub fn get_field(&self, key: &str) -> LResult<Option<&FieldValue>> {
        let field_key = key.try_into()?;
        Ok(self.fields.get(&field_key))
    }

    #[inline]
    pub fn tags_count(&self) -> usize {
        self.tags.len()
    }

    #[inline]
    pub fn fields_count(&self) -> usize {
        self.fields.len()
    }
}

impl Display for Entry {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        // Syntax
        // <measurement>[,<tag_key>=<tag_value>[,<tag_key>=<tag_value>]] <field_key>=<field_value>[,<field_key>=<field_value>] [<timestamp>]
        let mut builder = String::with_capacity(32);
        builder.push_str(self.measurement.as_str());
        // tags
        for (k, v) in &self.tags {
            builder.push_str(",");
            unsafe {
                builder.push_str(&*k.get_string_ref());
                builder.push_str("=");
                builder.push_str(&*v.get_string_ref());
            }
        }
        if self.fields.len() > 0 {
            builder.push_str(" ");
            for (i, (k, v)) in self.fields.iter().enumerate() {
                if i > 0 {
                    builder.push_str(",");
                }
                unsafe {
                    builder.push_str(&*k.get_string_ref());
                }
                builder.push_str("=");
                v.print(&mut builder);
            }
        }
        write!(f, "{} {}", builder, self.ts_nano())
    }
}

impl Debug for Entry {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Entry")
    }
}

impl EntryBuilder {
    #[inline]
    pub fn create<S: Into<String>>(measurement: S) -> Self {
        Self {
            measurement: Some(measurement.into()),
            ts: None,
            tags: BTreeMap::new(),
            fields: BTreeMap::new(),
        }
    }

    #[inline]
    pub fn measurement(mut self, measurement: String) -> Self {
        self.measurement = Some(measurement);
        self
    }

    #[inline]
    pub fn ts(mut self, ts: SystemTime) -> Self {
        self.ts = Some(ts);
        self
    }

    #[inline]
    pub fn add_tag(mut self, key: TagKey, value: TagValue) -> Self {
        self.tags.insert(key, value);
        self
    }

    #[inline]
    pub fn add_field(mut self, key: FieldKey, value: FieldValue) -> Self {
        self.fields.insert(key, value);
        self
    }

    #[inline]
    pub fn build(self) -> LResult<Entry> {
        let EntryBuilder { measurement, ts, tags, fields } = self;
        if measurement.is_none() {
            return Err(Error::EntryLineProtocolErr("missing measurement name".into()));
        }
        let ts = ts.unwrap_or_else(|| SystemTime::now());
        Ok(Entry {
            measurement: measurement.unwrap(),
            ts,
            tags,
            fields,
        })
    }
}

impl Default for EntryBuilder {
    fn default() -> Self {
        Self {
            measurement: None,
            ts: None,
            tags: Default::default(),
            fields: Default::default(),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::entry::Entry;
    use crate::LResult;
    use crate::value::FieldValue;

    #[test]
    fn test_display() -> LResult<()> {
        let entry = Entry::new_builder("m")
            .add_tag("t0".try_into()?, "aaa".try_into()?)
            .add_tag("t1".try_into()?, "bbb".try_into()?)
            .add_field("age".try_into()?, FieldValue::I64(32))
            .build()?;
        assert_eq!(
            format!("m,t1=bbb,t0=aaa age=32 {}", entry.ts_nano()),
            entry.to_string()
        );
        Ok(())
    }
}